/**
 * 
 */
package edu.vanderbilt.vshare.multicast;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.zip.CRC32;

/**
 * class for reading, parsing, or creating a data packet
 * 
 * @author jedmondson
 *
 */
public class DataPacket {

	/**
	 * Constructor
	 */
	
	public DataPacket ()
	{
		
	}

	/**
	 * Constructor for a client that has received a packet from a server
	 * 
	 * @param     total              packet received from the server
	 * @param     payloadSize        payload size for packet (default 16KB)
	 *                               anything but verification)
	 */
	
	public DataPacket (byte[] total, int payloadSize)
	{
		read (total, payloadSize);
	}


	/**
	 * method for server to construct a packet out of header parts and
	 * a payload
	 * 
	 * @param     fileid             file id that the packet belongs to
	 * @param     number             file's payload index (-1 is metadata)
	 * @param     payload            chunk of data that resides at index
	 */
	
	public DataPacket(Integer fileid, Integer number, byte[] payload) {
		// setup the private variables
		this.fileid = fileid;
		this.number = number;
		this.payload = payload;
		
		createTotal (fileid, number, payload);
	}

	/**
	 * method for a client that has received a packet from a server to read
	 * the header information, checksum, and payload from that packet
	 * 
	 * @param     total              packet received from the server
	 * @param     payloadSize        payload size for packet (default 16KB)
	 *                               anything but verification)
	 * @return                       true if successful
	 */
	
	public boolean read (byte[] total, int payloadSize)
	{
		this.total = total; 
		ByteArrayInputStream bis = new ByteArrayInputStream(total);  
		DataInputStream dis = new DataInputStream(bis);  
		
		payload = new byte[payloadSize];
		
		try
		{
			fileid = dis.readInt();
			number = dis.readInt();
			dis.readFully(payload, 0, payload.length);
			checksum = dis.readLong();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}

		// The following has been commented out for release versions. However,
		// when uncommented, this will document the problems with using
		// ByteArrayOutputStream instead of the hand made Utility.copy function
		
		/*
		if (number == 6)
		{
			System.out.println ("**Number 6 checksum is " + checksum);
			
			StringBuilder output = new StringBuilder ();

			
			for (int i = 40 + 8; i < 44 + 8; ++i)
				output.append("total[" + i + "]:" + (int)total[i] + "\n");
				
			
			for (int i = 40; i < 44; ++i)
				output.append("payload[" + i + "]:" + (int)payload[i] + "\n");

			System.out.print ("**Number 6 payload info:\n" + output.toString());
		}
		*/
		
		return checksum == getChecksum (total, payloadSize);
	}

	/**
	 * method for server to construct a packet out of header parts and
	 * a payload
	 * 
	 * @param     fileid             file id that the packet belongs to
	 * @param     number             file's payload index (-1 is metadata)
	 * @param     payload            chunk of data that resides at index
	 * @return                       compressed byte array of header parts, payload,
	 *                               and a checksum of the header parts and payload
	 */
	
	private byte [] createTotal (Integer fileid, Integer number, byte[] payload)
	{
		// total packet will be
		total = new byte [16 + payload.length];
		
		ByteArrayOutputStream bos = new ByteArrayOutputStream(16 + payload.length);  
		DataOutputStream dos = new DataOutputStream(bos);  
		try 
		{
			dos.writeInt(fileid);  
			dos.writeInt(number); 
			
			// ByteArrayOutputStream or DataOutputStream are broken
			// for writing bytearrays right no. I developed a custom
			// byte copy and this should work for now
			Utility.copy(bos.toByteArray(), 0, total, 0);
			Utility.copy(payload, 0, total, 8);
			
			dos.write(payload);
			
			checksum = getChecksum (total, payload.length);
			dos.writeLong(checksum);
			dos.flush();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}

		// again ByteArrayOutputStream or DataOutputStream are broken
		// we have to copy these by hand
		Utility.copy(bos.toByteArray(), 8 + payload.length, total, 8 + payload.length);

		// The following has been commented out for release versions. However,
		// when uncommented, this will document the problems with using
		// ByteArrayOutputStream instead of the hand made Utility.copy function
		
		/*
		if (number == 6)
		{
			System.out.println ("**Number 6 checksum is " + checksum);
			
			StringBuilder output = new StringBuilder ();

			
			for (int i = 40 + 8; i < 44 + 10; ++i)
				output.append("total[" + i + "]:" + (int)total[i] + "\n");
				
			
			for (int i = 40; i < 46; ++i)
				output.append("payload[" + i + "]:" + (int)payload[i] + "\n");
			
			
			System.out.print ("**Number 6 payload info:\n" + output.toString());
		}
		*/
		
		return total;
	}

	/**
	 * checks to see if this DataPacket is valid
	 * 
	 * @param     payloadSize        used to break the total packet into its
	 *                               needed pieces for verification
	 * @return                       true if the checksum is valid
	 */
	
	public boolean isValid (int payloadSize)
	{
		return checksum == getChecksum (total, payloadSize);
	}

	/**
	 * returns the checksum for header and payload data in the provided compressed
	 * byte array
	 * 
	 * @param     data               compressed header and payload data
	 * @param     payloadSize        used to break the total packet into its
	 *                               needed pieces for verification
	 * @return                       the checksum
	 */
	
	public long getChecksum (byte [] data, int payloadSize)
	{
		CRC32 checksum = new CRC32 ();
		checksum.update(data, 0, 8 + payloadSize);		
		
		return checksum.getValue();
	}

	/**
	 * compress the header, payload data, and checksum into a packet
	 * 
	 * @return                       compressed header, payload, and checksum
	 */
	
	public byte [] toBytes ()
	{
		return total;
	}
	
	/**
	 * @return the fileid
	 */
	public Integer getFileid() {
		return fileid;
	}

	/**
	 * @param fileid the fileid to set
	 */
	public void setFileid(Integer fileid) {
		this.fileid = fileid;
	}

	/**
	 * @return the number
	 */
	public Integer getNumber() {
		return number;
	}

	/**
	 * @param number the number to set
	 */
	public void setNumber(Integer number) {
		this.number = number;
	}

	/**
	 * @return the payload
	 */
	public byte[] getPayload() {
		return payload;
	}

	/**
	 * @param payload the payload to set
	 */
	public void setPayload(byte[] payload) {
		this.payload = payload;
	}

	private Integer fileid;
	private Integer number;
	private Long checksum;
	private byte [] payload;
	private byte [] total;
}
